<?php

/*
 * Copyright (c) 2012 Aalto University and University of Helsinki
 * MIT License
 * see LICENSE.txt for more information
 */

EasyRdf_Namespace::set('onki', 'http://schema.onki.fi/onki#');
EasyRdf_Namespace::set('void', 'http://rdfs.org/ns/void#');
EasyRdf_Namespace::set('pf', 'http://jena.hpl.hp.com/ARQ/property#');
EasyRdf_Namespace::set('text', 'http://jena.apache.org/text#');
EasyRdf_Namespace::set('ysa_meta', 'http://www.yso.fi/onto/ysa-meta/');
EasyRdf_Namespace::set('kunnat', 'http://www.yso.fi/onto/kunnat/');
EasyRdf_Namespace::set('skosext', 'http://purl.org/finnonto/schema/skosext#');

require_once('model/DataObject.php');
require_once('model/VocabularyDataObject.php');
require_once('model/Concept.php');
require_once('model/Collection.php');
require_once('model/VocabularyCategory.php');
require_once('model/Vocabulary.php');

require_once('model/sparql/GenericSparql.php');
require_once('model/sparql/BigdataSparql.php');
require_once('model/sparql/JenaTextSparql.php');

class breadCrumb {
  public $prefLabel;
  public $uri;
  public $narrowerConcepts;
  public $hiddenLabel;

  public function __construct($uri, $prefLabel) {
    $this->narrowerConcepts = array();
    $this->uri = $uri;
    $this->prefLabel = $prefLabel;
  }

  public function hideLabel() {
    if(!$this->hiddenLabel)
      $this->hiddenLabel = $this->prefLabel;
    $this->prefLabel = '...';
  }

  public function addNarrower($concept) {
    $this->narrowerConcepts[$concept->uri] = $concept;
  }
}

class Model {
  private $graph;

  public function __construct() {
    $this->graph = new EasyRdf_Graph();
    $this->graph->parse(file_get_contents(VOCABULARIES_FILE));
  }

  /**
   * Return all the vocabularies available.
   * @param boolean $categories wheter you want everything included in a subarray of
   * a category.
   */
  public function getVocabularyList($categories = true) {
    $cats = $this->getVocabularyCategories();
    foreach ($cats as $cat) {
      $catlabel = $cat->getTitle();

      // find all the vocabs in this category
      $vocs = $cat->getVocabularies();

      if (sizeof($vocs) > 0 && $categories)
        $ret[$catlabel] = $vocs;
      elseif (sizeof($vocs) > 0)
        $ret = array_merge($vocs, $ret);
    }
    return $ret;
  }

  public $crumbs = array();
  
  /**
   * Makes a query for the transitive broaders of a concept and returns the concepts hierarchy processed for the view.
   * @param string $vocab
   * @param string $lang
   * @param string $uri
   */
  public function getBreadCrumbs($vocab, $lang, $uri) {
    $broaders = $vocab->getConceptTransitiveBroaders($uri, 1000);
    $this->getCrumbs($broaders, $uri);
    $crumbs['combined'] = $this->combineCrumbs();
    $crumbs['breadcrumbs'] = $this->crumbs;
    return $crumbs;
  }

  /**
   * Takes the crumbs as a parameter and combines the crumbs if the path they form is too long.
   *
   */
  public function combineCrumbs() {
    $combined = array();
    foreach ($this->crumbs as $pathKey => $path) {
      $firstToCombine = true;
      $combinedPath = array();
      foreach ($path as $crumbKey => $crumb) {
        if($crumb->prefLabel === '...') {
          array_push($combinedPath, $crumb);
          if($firstToCombine) {
            $firstToCombine = false; 
          } else {
            unset($this->crumbs[$pathKey][$crumbKey]);
          }
        }
      }
      $combined[] = $combinedPath;
    }
    return $combined;
  }
  
  public function getCrumbs($bT, $uri, $path=null) {
    if(!isset($path))
      $path = array();
    if(isset($bT[$uri]['direct'])) {
      foreach ($bT[$uri]['direct'] as $broaderUri => $broaderLabel) {
        $newpath = array_merge($path, array(new breadCrumb($uri, $bT[$uri]['label'])));
        $this->getCrumbs($bT, $broaderUri, $newpath);
      }
    }
    else { // we have reached the end of a path and we need to start a new row in the 'stack'
      $path = array_merge($path, array(new breadCrumb($uri, $bT[$uri]['label'])));
      $index = 1;
      $length = sizeof($path);
      $limit = $length - 5;
      foreach ($path as $crumb) {
        if ($length > 5 && $index > $length-$limit) { // displays 5 concepts closest to the concept.
          $crumb->hideLabel();
        }
        $index++;
      }
      $this->crumbs[] = array_reverse($path);
    }
  }

  /**
   * Makes a SPARQL-query to the endpoint that retrieves concept
   * references as it's search results.
   * @param string $term the term that is looked for eg. 'cat'.
   * @param string $vocid vocabulary id eg. 'yso', or empty for global search.
   * @param string $lang language parameter eg. 'fi' for Finnish.
   * @param string $type limit search to concepts of the given type
   * @param string $parent limit search to concepts which have the given concept as parent in the transitive broader hierarchy
   * @param string $group limit search to concepts which are in the given group
   * @param int $offset optional parameter for search offset.
   * @param int $maxhits optional paramater for maximum amount of results.
   */
  public function searchConcepts($term, $vocid, $lang, $type = null, $parent=null, $group=null, $offset = 0, $limit = DEFAULT_SEARCH_LIMIT) {
    $term = trim($term);
    if ($term == "" || $term == "*")
      return array(); // don't even try to search for empty prefix
    if ($vocid) { // search within vocabulary
      $voc = $this->getVocabulary($vocid);
      $sparql = $voc->getSparql();
    } else { // global search
      $voc = null;
      $sparql = $this->getDefaultSparql();
    }
    if (!$type) $type = 'skos:Concept';
    
    $result = $sparql->queryConcepts($term, $lang, $limit, $offset, $type, $parent, $group);
    $ret = array();

    foreach ($result as $row) {
      $hit = array();
      $hit['uri'] = $row->s->getUri();

      if ($vocid)
        // search within vocabulary
        $hit['vocab'] = $vocid;
      else { // global search
        try {
          $voc = $this->getVocabularyByGraph($row->graph);
          $hit['vocab'] = $voc->getId();
        } catch (Exception $e) {
          trigger_error($e->getMessage(), E_USER_WARNING);
          $voc = null;
          $hit['vocab'] = "???";
        }
      }
      
      if ($voc !== null)
        $hit['localname'] = $voc->getLocalName($row->s->getUri());
      else
        $hit['localname'] = $row->s->localName();
      $hit['voc'] = $voc;

      // if uri is a external vocab uri that is included in the current vocab
      if ($hit['localname'] == $hit['uri']) {
        $voc = $this->guessVocabularyFromURI($hit['uri']);
        $hit['exvocab'] = $voc ? $voc->getId() : null;
      }

      $hit['prefLabel'] = $row->label->getValue();
      if ($row->label->getLang() != $lang) {
        $hit['lang'] = $row->label->getLang();
      }
      if (isset($row->alabel)) {
        $hit['altLabel'] = $row->alabel->getValue();
      }
      $ret[] = $hit;
    }
    return $ret;
  }

  /**
   * Function for performing a search for concepts and their data fields.
   * @param string $term searchterm eg. 'cat'
   * @param string $vocid which vocabulary is the search done in eg. 'yso', or empty for global search
   * @param string $lang eg. 'fi'
   * @param integer $offset used for offsetting the result set eg. '20'
   * @param integer $limit upper count for the search results eg. '10'
   */
  public function searchConceptsAndInfo($term, $vocid, $lang, $offset = 0, $limit = 20) {
    $hits = $this->searchConcepts($term, $vocid, $lang, null, null, null, $offset, $limit);

    $ret['hit'] = $hits;
    $uris = array();
    foreach ($hits as $hit)
      $uris[] = $hit['uri'];
    if ($vocid) {
      $voc = $this->getVocabulary($vocid);
      $sparql = $voc->getSparql();
      $hgc = $voc->getHasGlobalCollections();
    } else {
      $hgc = false;
      $sparql = $this->getDefaultSparql();
    }
    $ret = $sparql->queryConceptInfo($uris, $hgc, $lang, $vocid);

    // For marking that the concept has been found through an alternative label.
    foreach ($hits as $hit)
      if (isset($hit['altlabel']) && isset($ret[$hit['uri']]))
        $ret[$hit['uri']]->setFoundBy($hit['altlabel']);

    return $ret;
  }

  private function createDataObjects($class, $resarr) {
    $ret = array();
    foreach ($resarr as $res)
      $ret[] = new $class($this, $res);
    return $ret;
  }

  public function getVocabularies() {
    $vocs = $this->graph->allOfType('onki:Vocabulary');
    return $this->createDataObjects("Vocabulary", $vocs);
  }

  public function getVocabulariesInCategory($cat) {
    $vocs = $this->graph->resourcesMatching('dc:subject', $cat);
    return $this->createDataObjects("Vocabulary", $vocs);
  }

  public function getVocabularyCategories() {
    $cats = $this->graph->allOfType('skos:Concept');
    return $this->createDataObjects("VocabularyCategory", $cats);
  }

  public function getVocabulary($vocid) {
    $vocs = $this->getVocabularies();
    foreach ($vocs as $voc) {
      if ($voc->getId() == $vocid) {
        return $voc;
      }
    }
    throw new Exception("Vocabulary id '$vocid' not found in configuration.");
  }

  /**
   * Return the vocabulary that is stored in the given graph on the given endpoint.
   *
   * @param $graph string graph URI
   * @param $endpoint string endpoint URL (default SPARQL endpoint if omitted)
   * @return Vocabulary vocabulary of this URI, or null if not found
   */
  public function getVocabularyByGraph($graph, $endpoint = DEFAULT_ENDPOINT) {
    $vocs = $this->getVocabularies();
    foreach ($vocs as $voc) {
      if ($voc->getGraph() == $graph && $voc->getEndpoint() == $endpoint)
        return $voc;
    }
    throw new Exception( "no vocabulary found for graph $graph and endpoint $endpoint");
  }

  /**
   * Guess which vocabulary a URI originates from, based on the declared
   * vocabulary URI spaces.
   *
   * @param $uri string URI to search
   * @return Vocabulary vocabulary of this URI, or null if not found
   */
  public function guessVocabularyFromURI($uri) {
    $vocs = $this->getVocabularies();
    foreach ($vocs as $voc) {
      if (strpos($uri, $voc->getUriSpace()) === 0)
        return $voc;
    }
    return null;
  }

  public function getSparqlImplementation($dialect, $endpoint, $graph) {
    $classname = $dialect . "Sparql";
    return new $classname($endpoint, $graph, $this);
  }

  public function getDefaultSparql() {
    return $this->getSparqlImplementation(DEFAULT_SPARQL_DIALECT, DEFAULT_ENDPOINT, '?graph');
  }

}

?>
